/*
 * Copyright (c) 2005, 2008, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.jmx.mbeanserver;

import static com.sun.jmx.mbeanserver.Util.*;

import java.lang.reflect.Method;
import java.util.Map;

import javax.management.Attribute;
import javax.management.MBeanServerConnection;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;

/**
 * <p>Helper class for an {@link InvocationHandler} that forwards methods from an
 * MXBean interface to a named
 * MXBean in an MBean Server and handles translation between the
 * arbitrary Java types in the interface and the Open Types used
 * by the MXBean.</p>
 *
 * @since 1.6
 */
public class MXBeanProxy {

  public MXBeanProxy(Class<?> mxbeanInterface) {

    if (mxbeanInterface == null) {
      throw new IllegalArgumentException("Null parameter");
    }

    final MBeanAnalyzer<ConvertingMethod> analyzer;
    try {
      analyzer =
          MXBeanIntrospector.getInstance().getAnalyzer(mxbeanInterface);
    } catch (NotCompliantMBeanException e) {
      throw new IllegalArgumentException(e);
    }
    analyzer.visit(new Visitor());
  }

  private class Visitor
      implements MBeanAnalyzer.MBeanVisitor<ConvertingMethod> {

    public void visitAttribute(String attributeName,
        ConvertingMethod getter,
        ConvertingMethod setter) {
      if (getter != null) {
        getter.checkCallToOpen();
        Method getterMethod = getter.getMethod();
        handlerMap.put(getterMethod,
            new GetHandler(attributeName, getter));
      }
      if (setter != null) {
        // return type is void, no need for checkCallToOpen
        Method setterMethod = setter.getMethod();
        handlerMap.put(setterMethod,
            new SetHandler(attributeName, setter));
      }
    }

    public void visitOperation(String operationName,
        ConvertingMethod operation) {
      operation.checkCallToOpen();
      Method operationMethod = operation.getMethod();
      String[] sig = operation.getOpenSignature();
      handlerMap.put(operationMethod,
          new InvokeHandler(operationName, sig, operation));
    }
  }

  private static abstract class Handler {

    Handler(String name, ConvertingMethod cm) {
      this.name = name;
      this.convertingMethod = cm;
    }

    String getName() {
      return name;
    }

    ConvertingMethod getConvertingMethod() {
      return convertingMethod;
    }

    abstract Object invoke(MBeanServerConnection mbsc,
        ObjectName name, Object[] args) throws Exception;

    private final String name;
    private final ConvertingMethod convertingMethod;
  }

  private static class GetHandler extends Handler {

    GetHandler(String attributeName, ConvertingMethod cm) {
      super(attributeName, cm);
    }

    @Override
    Object invoke(MBeanServerConnection mbsc, ObjectName name, Object[] args)
        throws Exception {
      assert (args == null || args.length == 0);
      return mbsc.getAttribute(name, getName());
    }
  }

  private static class SetHandler extends Handler {

    SetHandler(String attributeName, ConvertingMethod cm) {
      super(attributeName, cm);
    }

    @Override
    Object invoke(MBeanServerConnection mbsc, ObjectName name, Object[] args)
        throws Exception {
      assert (args.length == 1);
      Attribute attr = new Attribute(getName(), args[0]);
      mbsc.setAttribute(name, attr);
      return null;
    }
  }

  private static class InvokeHandler extends Handler {

    InvokeHandler(String operationName, String[] signature,
        ConvertingMethod cm) {
      super(operationName, cm);
      this.signature = signature;
    }

    Object invoke(MBeanServerConnection mbsc, ObjectName name, Object[] args)
        throws Exception {
      return mbsc.invoke(name, getName(), args, signature);
    }

    private final String[] signature;
  }

  public Object invoke(MBeanServerConnection mbsc, ObjectName name,
      Method method, Object[] args)
      throws Throwable {

    Handler handler = handlerMap.get(method);
    ConvertingMethod cm = handler.getConvertingMethod();
    MXBeanLookup lookup = MXBeanLookup.lookupFor(mbsc);
    MXBeanLookup oldLookup = MXBeanLookup.getLookup();
    try {
      MXBeanLookup.setLookup(lookup);
      Object[] openArgs = cm.toOpenParameters(lookup, args);
      Object result = handler.invoke(mbsc, name, openArgs);
      return cm.fromOpenReturnValue(lookup, result);
    } finally {
      MXBeanLookup.setLookup(oldLookup);
    }
  }

  private final Map<Method, Handler> handlerMap = newMap();
}
